package com.sunday.common.statemachine.study.e12_Using_Extended_State;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineEventResult;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Scanner;
import java.util.UUID;

/**
 * stateMachine.sendEventCollect(Mono.just(message1)) 和 stateMachine.sendEvents(Flux.just(message1, message2, message3)) 的主要区别在于：
 * <p>
 * 1. 返回类型不同：
 * stateMachine.sendEventCollect(Mono.just(message1)) 返回的是一个 Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>> 类型的结果。也就是说，它会收集发送事件的所有结果，返回一个包含结果的 List。Mono 代表一个异步操作，返回一个单一结果，而 List<StateMachineEventResult> 代表多个可能的结果。
 * stateMachine.sendEvents(Flux.just(message1, message2, message3)) 返回的是 Flux<Void> 类型的结果，它表示一个事件流，事件流是异步处理的，并且没有返回具体的结果类型（Void 表示没有特别的结果值）。
 * 2. 处理方式的区别：
 * sendEventCollect：这个方法在发送单个事件（如 Mono.just(message1)）时，会收集该事件的处理结果，并且返回一个 Mono，这个 Mono 在事件完成时会包含一个列表 List<StateMachineEventResult>，它表示事件处理的结果。
 * 这个方法会等事件的执行结果返回后才完成。因此，它是一种 同步 的方式，等待并收集事件的处理结果。
 * sendEvents：这个方法用于发送多个事件（如 Flux.just(message1, message2, message3)），并且它是 异步 发送的。多个事件会并发发送，而不等待前一个事件的处理结果，因此更适合用来处理多个并行事件。
 * 3. 执行顺序：
 * sendEventCollect 处理的是一个事件，且其结果会被收集到一个列表中，最终通过 Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>> 返回，意味着它会处理一个事件并返回该事件的结果。它适用于 单个事件 的结果收集。
 * sendEvents 处理的是多个事件，并且是并行处理的。它适合用来处理多个独立的事件流。
 * 4. 适用场景：
 * sendEventCollect 适合于需要等待事件执行完并收集处理结果的场景，比如你希望知道某个事件执行的具体结果并返回。
 * sendEvents 适用于处理多个事件，并且这些事件不需要按照顺序执行（可以并行处理）。
 * 总结：
 * sendEventCollect：发送单个事件，收集事件结果，并且返回一个包含事件结果的 Mono 对象。
 * sendEvents：发送多个事件，结果是异步的，并且没有收集事件结果的返回值。
 * 在实际使用中，如果你只关心单个事件的处理结果并且希望等待这个结果，可以使用 sendEventCollect；如果你想并行处理多个事件并不关心每个事件的具体结果，可以使用 sendEvents。
 * <p>
 * stateMachine.sendEventCollect(Mono.just(message1)) 返回的是一个 Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>>，其中的 List<StateMachineEventResult<OrderStates, OrderEvents>> 用来表示多个事件的处理结果。尽管你发送的只包含一个事件（Mono.just(message1)），返回的仍然是一个 列表，因为 sendEventCollect 方法设计成可以处理多个事件并返回它们的结果。
 * <p>
 * 让我们详细拆解：
 * <p>
 * 1. Mono 和 Flux 之间的区别：
 * Mono：表示 单一的异步结果。它最多包含一个元素，可以是成功的元素，也可以是失败的状态（错误）。
 * Flux：表示一个 异步的流，可以包含多个元素（0个、1个或多个）。
 * 2. sendEventCollect 方法的作用：
 * sendEventCollect 是一种特殊的事件发送方式，它会等待事件的处理完成并收集所有事件的结果。即使你只发送了一个事件，它会仍然返回一个包含该事件结果的 List，因为该方法是为将来可能需要处理多个事件的场景设计的。
 * <p>
 * Mono.just(message1)：表示你只有一个消息（事件）。
 * sendEventCollect：这个方法会等待这个消息的处理，并且当处理完成时，它会返回一个包含事件结果的列表（即使列表中只有一个元素）。
 * 因此，尽管你发送的事件只有一个，sendEventCollect 仍然会返回一个列表，因为它的返回类型设计为 List<StateMachineEventResult>，这个列表中包含了多个事件的处理结果。如果你只发送一个事件，那么列表中就只有一个元素。
 * <p>
 * 3. 为什么是 List：
 * sendEventCollect 方法本质上是为了收集多个事件的处理结果，因此它返回的是 List<StateMachineEventResult<S, E>>，即便只有一个事件被发送。在 Spring State Machine 的设计中，事件的结果总是被收集为一个列表，因为这个方法的目标是允许你处理多个事件，并统一收集它们的处理结果。
 * <p>
 * 所以，即使你只发送了一个事件，它仍然返回一个 List，这个列表中有一个元素。返回的是 List，而不是单一元素，因为它的设计目的就是为了处理多事件场景。
 * <p>
 * 4. 示例解释：
 * 假设你发送的消息只有一个：
 * <p>
 * java
 * 复制代码
 * Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>> results =
 * stateMachine.sendEventCollect(Mono.just(message1));
 * 在这种情况下，results 将会是一个 Mono 对象，包含一个 List，该 List 包含了 StateMachineEventResult 类型的一个元素，这个元素代表了 message1 事件的处理结果。
 * <p>
 * 5. 处理过程：
 * 发送事件：你发送 message1，sendEventCollect 会等待事件处理。
 * 结果收集：事件处理完成后，所有的处理结果会被收集到一个 List 中，即使你只发送了一个事件。
 * 返回类型：Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>> 返回的 Mono 包装了一个 List，这个 List 中包含了事件的处理结果。
 * 总结：
 * sendEventCollect 返回的是一个 Mono<List<...>>，因为它是为可能发送多个事件并收集结果的场景设计的。
 * 即便你只发送一个事件，它仍然会返回一个 包含该事件结果的列表，因为返回类型是 List。
 */

@Slf4j
@Component
public class MyCommandLineRunner2 implements CommandLineRunner {

    @Autowired
    private StateMachine<OrderStates, OrderEvents> stateMachine;

    @Override
    public void run(String... args) throws Exception {

        while (true) {
            Scanner scan = new Scanner(System.in);//实例化Scanner对象
            String input = scan.nextLine();//接受用户输入的字符串
            // 判断用户输入是否为"exit"
            if ("exit".equalsIgnoreCase(input)) {
                log.info("程序已退出");
                break;
            } else {
                log.info("你输入的文本是: {}", input);
                try {

                    Message<OrderEvents> message1 = MessageBuilder
                            .withPayload(OrderEvents.valueOf(input.toUpperCase()))
                            .setHeader("OrderId", UUID.randomUUID())
                            .setHeader("stockAvailable", true)
//                            .setHeader(StateMachineMessageHeaders.HEADER_DO_ACTION_TIMEOUT, 5000)
                            .build();


                    Mono<List<StateMachineEventResult<OrderStates, OrderEvents>>> results =
                            stateMachine.sendEventCollect(Mono.just(message1));

                    results.subscribe(stateMachineEventResults -> {
                        stateMachineEventResults.stream().forEach(stateMachineEventResult -> {
                            log.info("Results: {}", stateMachineEventResult);
                        });
                    });

                } catch (Exception e) {
                    log.error("无法识别的事件: {} , 异常: {}", input, e.toString());
                }
            }

        }

    }

    /**
     * https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#using-eventtrigger
     * 用EventTrigger
     * EventTrigger是最有用的触发器，因为它允许您 通过向状态机发送事件来直接与状态机交互。这些 事件也称为信号。您可以向过渡添加触发器 通过在配置期间将状态与其关联。 以下示例显示了如何执行此操作：
     *
     * @Autowired
     * StateMachine<String, String> stateMachine;
     *
     * void signalMachine() {
     * 	stateMachine
     * 		.sendEvent(Mono.just(MessageBuilder
     * 			.withPayload("E1").build()))
     * 		.subscribe();
     *
     * 	Message<String> message = MessageBuilder
     * 			.withPayload("E2")
     * 			.setHeader("foo", "bar")
     * 			.build();
     * 	stateMachine.sendEvent(Mono.just(message)).subscribe();
     * }
     *
     * 无论您是发送一个事件还是多个事件，结果始终是一个序列 的结果。之所以如此，是因为在存在多个 reqions 的情况下，结果将 从这些区域中的多台计算机返回。这是 with 方法，该方法给出结果列表。方法 本身只是一个语法 Sugar Collecting As 列表。如果有 只有一个地区，此列表包含一个结果。sendEventCollectFlux
     *
     * Message<String> message1 = MessageBuilder
     * 	.withPayload("E1")
     * 	.build();
     *
     * Mono<List<StateMachineEventResult<String, String>>> results =
     * 	stateMachine.sendEventCollect(Mono.just(message1));
     *
     * results.subscribe();
     *
     * 在订阅返回的 flux 之前，什么都不会发生。从 StateMachineEventResult 中了解更多信息。
     *      前面的示例通过构造 wrapping 来发送事件 a 并订阅返回的结果。 让 我们向事件添加任意的额外信息，然后该信息可见 到实施操作的时间（例如）。MonoMessageFluxMessageStateContext
     *
     *      消息标头通常会一直传递，直到机器运行 完成特定事件。例如，如果事件导致 transition 转换为 state 中，该 state 具有匿名 transition 到 state 中，原始事件可用于 state 中的操作或守卫。ABB
     *      也可以发送消息，而不仅仅是发送 一个带有 .FluxMono
     *
     *      Message<String> message1 = MessageBuilder
     *      .withPayload("E1")
     *      .build();
     *      Message<String> message2 = MessageBuilder
     *      .withPayload("E2")
     *      .build();
     *
     *      Flux<StateMachineEventResult<String, String>> results =
     *      stateMachine.sendEvents(Flux.just(message1, message2));
     *
     *      results.subscribe();
     *
     */


}
